package cn.yeziji.utils;

import cn.yeziji.entity.TyCloudEntity;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.logging.Logger;

/**
 * @author gzkemays
 * @date 2021/2/3 10:21
 * @description tycloudAuthorization、tycloudSign 有2个重载方法，区别在于 boolean，默认为 true。 如果传的是 true
 *     则不需要对传输数据进行 MD5 加密。如果需要手动进行特殊的MD5加密，则设置为 false，一般走默认值即可。 当测试数据时，推荐使用 false，这样可以人为的复制成功案例的 MD5
 *     数据进行有效的测试。
 * @apiNote 1、文件预热(Post)：/api/v1/preloadmanage/create 参数：account_id (AccessKey) 和 values ( values 是
 *     List 列表 ) 两个是 required 2、预热查询(Get)：/api/v1/preloadmanage/query
 *     参数：account_id(required)、url、submit_id、task_id、type(0: 代表按照时间查询，此时起止时间为必传参数;1 代表按照 submit_id
 *     查询，此时 submit_id 为必传参 数,2:代表按照 task_id 查询，此时 task_id 为必传参数； 默认为 0，按照时间查询
 *     );start_time、end_time、page、page_size 3、预取任务额度查询(Get)：/api/v1/preloadmanage/quota
 *     参数：account_id(required) response
 * @result 响应数据： 100000 正确返回 10000 正确返回 100001 时间格式错误 100002 开始时间错误 100003 结束时间错误 100004 结束时间小于开始时间
 *     100005 超出可查询范围 100006 请求参数错误 100007 必填参数没有添加 100008 返回值格式不正确 100009 无查询权限 100010 服务暂时不可用
 *     100011 找不到指定的 API 100012 请求错误 100013 系统错误 100014 未知错误 100015 token 错误 100016 缺少域名或域名 ID 参数
 *     100017 获取 token 失败 100018 用户未登录 100019 用户认证失败 100020 需要 GET 请求 100021 需要 POST 请求 100022
 *     没有找到当前时间日志文件 100023 域名不存在 100024 产品类型不存在 100025 地区不存在 100027 计费类型不存在 100028 查询条件不存在 100029
 *     操作失败 100030 域名已存在 100031 域名未备案 100032 存在未完成工单 100033 请求超时 100036 节点列表参数错误 100046 回源信息参数错误
 *     100056 文件过期时间设置错误 100066 文件过期时间内部参数设置错误 100076 目录过期时间设置错误 100086 目录过期时间内部参数设置错误 100096
 *     自定义回源请求头设置错误 100116 自定义回源请求头内部参数设置错误 100126 自定义响应头设置错误 100136 自定义响应头内部参数设置错误 100146 缓存参数设置错误
 *     100156 缓存参数内部参数设置错误 100166 Referer 白名单设置错误 100176 Referer 白名单内部参数设置错误 100186 Referer 黑名单设置错误
 *     100196 Referer 黑名单内部参数设置错误 110001 域名已经存在或正在配置中，无法操作 110002 域名 ID 或域名不存在 110003 域名操作配置中无法删除
 *     110004 域名操作配置中无法停止 110005 域名操作配置中无法启用 110006 缺少节点列表参数 110007 查询域名流量失败 110008 查询开始时间参数错误
 *     110009 查询结束时间参数错误 110010 查询类型参数错误 110011 查询命中率失败 110012 查询带宽流量失败 110013 查询节点 IP 失败 110014
 *     GSLB 下发配置失败 110015 缓存配置下发失败 110016 缓存配置更新失败 110017 GSLB 配置删除失败 110018 缺少备案号参数 110019
 *     域名备案号查询失败 110020 域名和备案号不匹配 110021 缺少域名业务类型参数 110022 ip 白名单参数错误 110023 ip 黑名单参数错误 110024 CMDB
 *     存入失败 110025 节点创建失败 110026 节点查询失败 110027 节点更新失败 110028 缺少域名状态参数 110029 CMDB 删除失败 110030 节点删除失败
 *     110031 https 状态参数错误 110032 https 基础信息参数错误 110033 https 基础信息参数类型错误 110034 https 基础信息强制跳转参数错误
 *     110035 https 基础信息回源控制参数错误 110036 缺少 https 公钥信息参数 110037 缺少 https 私钥信息参数 110038 缺少 certificate
 *     参数 110039 缺少 client_id 参数 110040 缺少 origin_protocol 参数 110041 缺少 auto_https_redirect 参数
 */
public class TyCloudUtils {
  static Logger log = Logger.getLogger("TyCloudUtils");
  //  static Logger log = LoggerFactory.getLogger(TyCloudUtils.class);
  // HOST 访问地址
  public static final String HOST = "cdnapi.ctyun.cn";
  // AccessKey 客户端ID
  public static final String AK = "ee821339cc054bcd819e3ee84fbd6e34";
  // SecurityKey 客户端密钥
  public static final String SK = "NrSZEDeW5TH+Kx43B29gRelejedAyRh1uqZGGbVCpGo=";
  // 文件预热
  public static final String WARM_UP_CREATE = "/api/v1/preloadmanage/create";
  // 预热查询
  public static final String WARM_UP_QUERY = "/api/v1/preloadmanage/query";
  // 任务额度查询
  public static final String TASK_QUOTA_QUERY = "/api/v1/preloadmanage/quota";
  // 客户端ID,必须参数.
  public static final String ACCOUNT_ID = "account_id";
  // 开始时间和结束时间，在 query 预热查询时，是必须参数
  public static final String START_TIME = "start_time";
  public static final String END_TIME = "end_time";
  // 任务ID,具体值在 CDN 服务器上查看.
  public static final String TASK_TIME = "task_time";
  // 提交任务ID
  public static final String SUBMIT_ID = "submit_id";
  // 查询分类,默认为 0,当 1 时 SUBMIT_ID 为必须参数, 为 2 时 TASK_TIME 为必须参数.
  public static final String TYPE = "type";
  // 页数，默认 1
  public static final String PAGE = "page";
  // 每页条数 默认 50
  public static final String PAGE_SIZE = "page_size";

  /**
   * 生成密钥 Authorization
   *
   * @param entity
   * @param security
   * @param accessKey
   * @return
   */
  public static String tycloudAuthorization(
          TyCloudEntity entity, String security, String accessKey) {
    String signature = tycloudSign(entity, security);
    return "CTYUN " + accessKey + ":" + signature;
  }

  public static String tycloudAuthorization(
      TyCloudEntity entity, String security, String accessKey, boolean tomd5) {
    String signature = tycloudSign(entity, security, tomd5);
    return "CTYUN " + accessKey + ":" + signature;
  }

  /**
   * 生成签名
   *
   * @param entity
   * @param security
   * @param tomd5
   * @return
   */
  public static String tycloudSign(TyCloudEntity entity, String security, boolean tomd5) {
    String contentMd5 =
        tomd5
            ? (StringUtils.isBlank(entity.getContent()) ? "" : stringToMD5(entity.getContent()))
            : entity.getContent();
    return splicingSign(entity, security, contentMd5);
  }

  public static String tycloudSign(TyCloudEntity entity, String security) {
    String contentMd5 =
        StringUtils.isBlank(entity.getContent()) ? "" : stringToMD5(entity.getContent());
    return splicingSign(entity, security, contentMd5);
  }

  /**
   * HmacSHA256 签名算法
   *
   * @param entity
   * @param security
   * @param contentMd5
   * @return
   */
  private static String splicingSign(TyCloudEntity entity, String security, String contentMd5) {
    String content =
        entity.getVerb()
            + "\n"
            + contentMd5
            + "\n"
            + entity.getContentType()
            + "\n"
            + entity.getDate()
            + "\n"
            + entity.getResource();
    String result = null;
    try {
      Mac hmacSha256 = Mac.getInstance("HmacSHA256");
      byte[] keyBytes = security.getBytes(StandardCharsets.UTF_8);
      hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
      byte[] hmacSha256Bytes = hmacSha256.doFinal(content.getBytes(StandardCharsets.UTF_8));
      result = Base64.getEncoder().encodeToString(hmacSha256Bytes);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return result;
  }

  /**
   * String 转 MD5
   *
   * @param plainText
   * @return
   */
  private static String stringToMD5(String plainText) {
    byte[] secretBytes;
    try {
      secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("没有md5算法");
    }
    StringBuilder md5code = new StringBuilder(new BigInteger(1, secretBytes).toString(16));
    for (int i = 0; i < 32 - md5code.length(); i++) {
      md5code.insert(0, "0");
    }
    return md5code.toString();
  }

  /** 操作枚举类型 */
  public enum OPERA {
    CREATE(WARM_UP_CREATE),
    QUERY(WARM_UP_QUERY),
    TASK_QUOTA(TASK_QUOTA_QUERY);
    private String TYPE;

    OPERA(String TYPE) {
      this.TYPE = TYPE;
    }
  }

  /**
   * 预热操作
   *
   * @param entity
   * @param opera
   * @throws IOException
   */
  public static String TyCloudWarmUp(TyCloudEntity entity, String opera) throws IOException {
    CloseableHttpResponse response = null;
    CloseableHttpClient client = HttpClientBuilder.create().build();
    String result;
    // 创建
    if (opera.equals(OPERA.CREATE.TYPE)) {
      HttpPost httpPost = tyCloudPost(entity, WARM_UP_CREATE);
      StringEntity stringEntity = new StringEntity(entity.getData().toString());
      httpPost.setEntity(stringEntity);
      response = client.execute(httpPost);
      // 预热和余额查询
    } else if (opera.equals(OPERA.QUERY.TYPE) || opera.equals(OPERA.TASK_QUOTA.TYPE)) {
      HttpGet httpGet = tyCloudGet(entity, opera);
      response = client.execute(httpGet);
    }
    result = EntityUtils.toString(response.getEntity(), "UTF-8");
    log.info(result);
    return result;
  }

  /**
   * POST 请求
   *
   * @param entity
   * @param url
   * @return
   */
  private static HttpPost tyCloudPost(TyCloudEntity entity, String url) {
    url = "https://" + HOST + url;
    String authorization = TyCloudUtils.tycloudAuthorization(entity, SK, AK);
    HttpPost post = new HttpPost(url);
    post.addHeader("Date", entity.getDate());
    post.addHeader("Authorization", authorization);
    post.addHeader("Host", HOST);
    post.addHeader("Content-Type", "application/json;charset=utf-8");
    return post;
  }

  /**
   * Get 请求
   *
   * @param entity
   * @param url
   * @return
   */
  private static HttpGet tyCloudGet(TyCloudEntity entity, String url) {
    JSONObject json = JSONObject.parseObject(entity.getData().toString());
    if (Objects.isNull(json.get("account_id"))) throw new RuntimeException("没有客户端ID值(account_id)");
    url = "https://" + HOST + url + "?account_id=" + json.get("account_id");
    // Query 必须要有时间戳
    if (entity.getResource().equals(WARM_UP_QUERY)) {
      if (Objects.isNull(json.get("start_time")) || json.get("start_time").equals("0"))
        throw new RuntimeException("没有开始时间戳(start_time)");
      if (Objects.isNull(json.get("end_time")) || json.get("end_time").equals("0"))
        throw new RuntimeException("没有结束时间戳(end_time)");
      url = url + "&start_time=" + json.get("start_time") + "&end_time=" + json.get("end_time");
      url = Objects.nonNull(json.get("url")) ? url + "&url=" + json.get("url") : url;
      // type 0 , 1 , 2 对应的 required 值不同。具体看 final 注释
      if (Objects.nonNull(json.get("type"))) {
        String type = json.get("type").toString();
        url = url + "&type=" + json.get("type");
        url = type.equals("1") ? url + "&submit_id=" + json.get("submit_id") : url;
        url = type.equals("2") ? url + "&task_id=" + json.get("task_id") : url;
      }
      url = Objects.nonNull(json.get("page")) ? url + "&page=" + json.get("page") : url;
      url =
          Objects.nonNull(json.get("page_size"))
              ? url + "&page_size=" + json.get("page_size")
              : url;
    }

    String authorization = TyCloudUtils.tycloudAuthorization(entity, SK, AK);
    HttpGet get = new HttpGet(url);
    get.addHeader("Date", entity.getDate());
    get.addHeader("Authorization", authorization);
    get.addHeader("Host", HOST);
    get.addHeader("Content-Type", "text/html");
    return get;
  }

  /**
   * 时间戳转换
   *
   * @param year 年
   * @param month 月
   * @param day 日
   * @param hours 小时
   * @param min 分钟
   * @param second 秒
   * @return
   */
  public static long tyCloudTimeStamp(
      int year, int month, int day, int hours, int min, int second) {
    return (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
            .parse(
                year + "-" + month + "-" + day + " " + hours + ":" + min + ":" + second,
                new ParsePosition(0))
            .getTime()
        / 1000;
  }

  /** 获取某月前的时间戳 */
  public static long getMonthBeforeTimeStamp(int month) {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.MONTH, -month);
    Date beforeDay = calendar.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(sdf.format(beforeDay), new ParsePosition(0)).getTime() / 1000;
  }
}
